<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>5.6.2-只读和浅只读</title>
</head>

<body>
    <div id="container"></div>
</body>
<script>
    /**
     * 实现深只读
     * - 还应该在 get 拦截函数内递归地调用 readonly 将数据包装成只读的代理对象，
     *    并将其作为返回值返回
     * - 在返回属性值之前，判断它是否是只读的，如果是只读的，
     *    则调用 readonly 函数对值进行包装，并把包装后的只读对象返回
     **/
    const bucket = new WeakMap();
    const ITERATE_KEY = Symbol();

    // 在 get 拦截函数内调用 track 函数追踪变化
    function track(target, key) {
        // 没有 activeEffect，直接 return
        if (!activeEffect) return
        let depsMap = bucket.get(target)
        if (!depsMap) {
            bucket.set(target, (depsMap = new Map()))
        }
        let deps = depsMap.get(key)
        if (!deps) {
            depsMap.set(key, (deps = new Set()))
        }
        // 把当前激活的副作用函数添加到依赖集合 deps 中
        deps.add(activeEffect)
        // deps 就是一个与当前副作用函数存在联系的依赖集合
        // 将其添加到 activeEffect.deps 数组中
        activeEffect.deps.push(deps) 
    }
    // 在 set 拦截函数内调用 trigger 函数触发变化
    function trigger(target, key, type) {
        const depsMap = bucket.get(target)
        if (!depsMap) return
        // 取得与 key 相关联的副作用函数
        const effects = depsMap.get(key)

        const effectsToRun = new Set()
        // 将与 key 相关联的副作用函数添加到 effectsToRun
        effects && effects.forEach(effectFn => {
            // 如果 trigger 触发执行的副作用函数与当前正在执行的副作用函数相同，则不触发执行
            if (effectFn !== activeEffect) {  
                effectsToRun.add(effectFn)
            }
        })

        // 只有当操作类型为 'ADD' 时或 DELETE 时，才触发与 ITERATE_KEY 相关联的副作用函数重新执行
        if (type === 'ADD' || type === 'DELETE') {
            // 取得与 ITERATE_KEY 相关联的副作用函数
            const iterateEffects = depsMap.get(ITERATE_KEY)
            // 将与 ITERATE_KEY 相关联的副作用函数也添加到 effectsToRun
            iterateEffects && iterateEffects.forEach(effectFn => { 
                if (effectFn !== activeEffect) {  
                    effectsToRun.add(effectFn)
                }
            })
        }

        effectsToRun.forEach(effectFn => {
            // 如果一个副作用函数存在调度器，则调用该调度器，并将副作用函数作为参数传递
            if (effectFn.options.scheduler) {  
                effectFn.options.scheduler(effectFn)  
            } else {
                // 否则直接执行副作用函数（之前的默认行为）
                effectFn() 
            }
        })
    }

    // 用一个全局变量存储被注册的副作用函数
    let activeEffect
    // effect 栈
    // 个人理解，始终都会留着一个是最先执行的
    const effectStack = []  
    function effect(fn, options = {}) {
        const effectFn = () => {
            cleanup(effectFn)
            // 当调用 effect 注册副作用函数时，将副作用函数赋值给 activeEffect
            activeEffect = effectFn
            // 在调用副作用函数之前将当前副作用函数压栈
            effectStack.push(effectFn)
            // 将 fn 的执行结果存储到 res 中
            const res = fn()  
            // 在当前副作用函数执行完毕后，将当前副作用函数弹出栈，并把 activeEffect 还原为之前的值
            effectStack.pop()
            activeEffect = effectStack[effectStack.length - 1]
            return res  
        }
        // 将 options 挂载到 effectFn 上
        effectFn.options = options 
        // activeEffect.deps 用来存储所有与该副作用函数相关的依赖集合
        effectFn.deps = []
        // 只有非 lazy 的时候，才执行
        if (!options.lazy) {  
            // 执行副作用函数
            effectFn()
        }
        // 将副作用函数作为返回值返回
        return effectFn 
    }

    function cleanup(effectFn) {
        // 遍历 effectFn.deps 数组
        for (let i = 0; i < effectFn.deps.length; i++) {
            // deps 是依赖集合
            const deps = effectFn.deps[i]
            // 将 effectFn 从依赖集合中移除
            deps.delete(effectFn)
        }
        // 最后需要重置 effectFn.deps 数组
        effectFn.deps.length = 0
    }

    /**
     * @param { Object } obj 
     * @param { Boolean } isShallow 代表是否为浅响应，默认为 false，即非浅响应
     * @param { Boolean } isReadonly 代表是否只读，默认为 false，即非只读
     * */ 
    function createReactive(obj, isShallow = false, isReadonly = false) {
        return new Proxy(obj, {
            get(target, key, receiver) {
                // 代理对象可以通过 raw 属性访问原始数据
                if (key === 'raw') {
                    return target
                }

                // 非只读的时候才需要建立响应联系
                if (!isReadonly) {
                    // 建立联系
                    track(target, key)
                }   
                
                const res = Reflect.get(target, key, receiver)
                
                // 如果是浅响应，则直接返回原始值
                if (isShallow) {
                    return res
                }

                // 如果属性值是对象，则递归调用 createReactive 函数
                if (typeof res === 'object' && res !== null) {
                    // 如果数据为只读，则调用 readonly 对值进行包装
                    return isReadonly ? readonly(res) : reactive(res) // 新增
                }

                // 返回属性值
                return res
            },
            // 拦截设置操作
            set(target, key, newVal, receiver) {
                // 如果是只读的，则打印警告信息并返回
                if (isReadonly) {
                    console.warn(`属性 ${key} 是只读的`)
                    return true
                }
                // 先获取旧值
                const oldVal = target[key]

                // 如果属性不存在，则说明是在添加新属性，否则是设置已有属性
                const type = Object.prototype.hasOwnProperty.call(target, key) ? 'SET' : 'ADD'

                // 设置属性值
                const res = Reflect.set(target, key, newVal, receiver)

                // target === receiver.raw 说明 receiver 就是 target 的代理对象
                if (target === receiver.raw) { // 解决原型上继承属性的问题
                    // 比较新值与旧值，只有当它们不全等，并且不都是 NaN 的时候才触发响应
                    if (oldVal !== newVal && (oldVal === oldVal || newVal === newVal)) {
                        // 把副作用函数从桶里取出并执行
                        trigger(target, key, type)
                    }
                }
                return res;
            },
            // 拦截 in 操作符
            has(target, key) {
                // 建立联系
                track(target, key)
                // 返回属性值
                return Reflect.has(target, key)
            },
            // 拦截for...in循环
            ownKeys(target) {
                // 将副作用函数与 ITERATE_KEY 关联
                track(target, ITERATE_KEY)
                return Reflect.ownKeys(target)
            },
            deleteProperty(target, key) {
                // 如果是只读的，则打印警告信息并返回
                if (isReadonly) {
                    console.warn(`属性 ${key} 是只读的`)
                    return true
                }
                // 检查被操作的属性是否是对象自己的属性
                const hadKey = Object.prototype.hasOwnProperty.call(target, key)
                // 使用 Reflect.deleteProperty 完成属性的删除
                const res = Reflect.deleteProperty(target, key)
                if (res && hadKey) {
                    // 只有当被删除的属性是对象自己的属性并且成功删除时，才触发更新
                    trigger(target, key, 'DELETE')
                }
                return res
            },
        })
    }
    
    // 深响应
    function reactive(obj) {
        return createReactive(obj)
    }

    // 浅响应
    function shallowReactive(obj) {
        return createReactive(obj, true)
    }

    // 只读
    function readonly(obj) {
        return createReactive(obj, false, true)
    }

    // 浅只读
    function shallowReadonly(obj) { // 新增 shallowReadonly 函数
        return createReactive(obj, true/* shallow */, true)
    }

    const obj = readonly({ foo: { bar: 1 } })
    obj.foo.bar = 2 // 抛出警告


    
</script>

</html>